//
//  CantripWorldController.m
//  Cantrip
//
//  Created by James Callender on Wed Apr 24 2002.
//  Copyright (c) 2001 James Callender. All rights reserved.
//

#import "CantripWorldController.h"

/*
static NSString *WorldToolbarItemConnect = @"WorldToolbarItemConnect";
static NSString *WorldToolbarItemSettings = @"WorldToolbarItemSettings";
static NSString *WorldToolbarItemClean = @"WorldToolbarItemClean";
static NSString *WorldToolbarItemSave = @"WorldToolbarItemSave";
static NSString *WorldToolbarItemNewWindow = @"WorldToolbarItemNewWindow";
*/

static NSString *WorldToolbarItemConnect;
static NSString *WorldToolbarItemSettings;
static NSString *WorldToolbarItemClean;
static NSString *WorldToolbarItemSave;
static NSString *WorldToolbarItemNewWindow;


@implementation CantripWorldController

+ (void)initialize
{
    WorldToolbarItemConnect = @"WorldToolbarItemConnect";
    WorldToolbarItemSettings = @"WorldToolbarItemSettings";
    WorldToolbarItemClean = @"WorldToolbarItemClean";
    WorldToolbarItemSave = @"WorldToolbarItemSave";
    WorldToolbarItemNewWindow = @"WorldToolbarItemNewWindow";
}

- (id)init
{
    if (self = [super init])
    {
	mWindowData = [[NSMutableData alloc] init];
	mCommandHistory = [[NSMutableArray alloc] init];
	mHistoryLocation = 0;
	beatCount = 0;
	
	uniqueID = [[[NSProcessInfo processInfo] globallyUniqueString] retain];
    }
    
    return self;
}

- (void)dealloc
{
    [mThreadConnection release];
    [mWindowData release];
    [mCommandHistory release];
    [mSocket release];
    [uniqueID release];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

+ (CantripWorldController *)activeController
{
    //return [[CantripDocumentController sharedDocumentController] activeController];
    return nil;
}

- (void)windowDidLoad
{
    mThreadConnection = [[NSConnection alloc] 
	initWithReceivePort:[NSPort port] sendPort:[NSPort port]];
    [mThreadConnection setRootObject:self];

    childNumber = [[self document] incrementChild];

    [[self window] setDelegate:self];
    [self setupToolbar];
    //[self setStateAndUpdate:WorldStartState];

    [oDividerView setIsPaneSplitter:YES];

    [oInputScrollView setHasVerticalScroller:YES];
    [oInputScrollView setDocumentView:oInputField];
    [oInputScrollView setDrawsBackground:YES];
    [oInputField setFrameSize:
	NSMakeSize([oInputField frame].size.width - [NSScroller scrollerWidth], 
	[oInputField frame].size.height)];
    [oInputField setDelegate:self];

    if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_2) {
		[oProgressWidget setStyle:NSProgressIndicatorSpinningStyle];
		[oProgressWidget setDisplayedWhenStopped:NO];
	} else {
		// Backwards compatability for 10.1.5 users... lame.
		[oProgressWidget removeFromSuperview];
		oProgressWidget = nil;
    }
    
    mSocket = [[CantripSocket alloc] init];
    [mSocket setDelegate:self];

    [[self window] selectKeyViewFollowingView:oTermView];
    [[self window] setAlphaValue: 0.999];
    [[self window] setOpaque: NO];
			
    [oInputScrollView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
    [[oInputScrollView contentView] setAutoresizesSubviews:YES];
    [oInputField setMaxSize:NSMakeSize(1.0e7, 1.0e7)];
    [oInputScrollView setHasVerticalScroller:YES];
    [oInputField setHorizontallyResizable:YES];
    [oInputField setVerticallyResizable:YES];
    [oInputField setAutoresizingMask:NSViewWidthSizable];
    [[oInputField textContainer] setWidthTracksTextView:YES];
    [[oInputField textContainer] setHeightTracksTextView:NO];
        
    IAC_STATE = 0;
    IAC_SB = 0;
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mainWindowDidChange:) 
						 name:NSWindowDidBecomeMainNotification object:nil]; 
    
    [oTermView setDelegate:self];
    
	[[self document] addObserver:self forKeyPath:@"data" options:NSKeyValueObservingOptionNew context:NULL];
	
    //[[[self document] worldData] addObserver:self forKeyPath:@"OutputBackgroundColor" options:NSKeyValueObservingOptionNew context:NULL];
    //[[[self document] worldData] addObserver:self forKeyPath:@"InputBackgroundColor" options:NSKeyValueObservingOptionNew context:NULL];
    //[[[self document] worldData] addObserver:self forKeyPath:@"OutputForegroundColor" options:NSKeyValueObservingOptionNew context:NULL];
    //[[[self document] worldData] addObserver:self forKeyPath:@"InputForegroundColor" options:NSKeyValueObservingOptionNew context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    /*
    [oInputField setBackgroundColor:[[[self document] worldData] objectForKey:@"InputBackgroundColor"]];
    [oInputField setTextColor:[[[self document] worldData] objectForKey:@"InputForegroundColor"]];
    [oTermView setBackgroundColor:[[[self document] worldData] objectForKey:@"InputBackgroundColor"]];
    [oInputField setTextColor:[[[self document] worldData] objectForKey:@"InputForegroundColor"]];*/
	NSBeep();
    [self loadPreferences];
}

- (void)controllerWasInitialized
{		
    if ([[[[self document] data] objectForKey:WorldConnectOnOpenKey] boolValue])
	    [self connect];
}

- (void)loadPreferences
{
    NSDictionary *settings = [[self world] data];
    
    [oTermView setTextColor:[settings objectForKey:WorldDisplayForegroundColorKey]];
    [oTermView setBackgroundColor:[settings objectForKey:WorldDisplayBackgroundColorKey]];
    if ([settings objectForKey:WorldFontNameKey] && [settings objectForKey:WorldFontSizeKey])
	[oTermView setFont:
	    [NSFont fontWithName:[settings objectForKey:WorldFontNameKey] size:[[settings objectForKey:WorldFontSizeKey] intValue]]];
    else [oTermView setFont:[NSFont fontWithName:@"Courier" size:12]];
    [oTermView setScrollBack:[[settings objectForKey:WorldScrollbackKey] intValue]];
    //[oTermView setNeedsDisplay:YES];
    [oTermView setTabSize:[[settings objectForKey:WorldTabSizeKey] intValue]];
    
    [oInputField setTextColor:[settings objectForKey:WorldInputForegroundColorKey]];
    [oInputField setBackgroundColor:[settings objectForKey:WorldInputBackgroundColorKey]];
    if ([settings objectForKey:WorldInputFontNameKey])
	[oInputField setFont:
	    [NSFont fontWithName:[settings objectForKey:WorldInputFontNameKey] size:[[settings objectForKey:WorldInputFontSizeKey] intValue]]];
    else [oInputField setFont:[NSFont systemFontOfSize:13]];
    
    [oInputScrollView setBackgroundColor:[settings objectForKey:WorldInputBackgroundColorKey]];

    [oTermView setAnsiPlainColors:
	    [settings objectForKey:WorldStandardANSIColorsKey]];
    [oTermView setAnsiIntenseColors:
	    [settings objectForKey:WorldIntenseANSIColorsKey]];
    [oTermView setUsesIntenseColors:[[settings objectForKey:WorldEnableIntenseColorsKey] boolValue]];
    [oTermView setBoldIntenseColors:[[settings objectForKey:WorldBoldIntenseColorsKey] boolValue]];
    [oTermView setSensativeAutoScroll:[[settings objectForKey:WorldSmartScrollingKey] boolValue]];
    [oTermView setAnsiEnabled:[[settings objectForKey:WorldEnableANSIColorsKey] boolValue]];
    [oTermView setBlinkingEnabled:[[settings objectForKey:WorldEnableTextBlinkingKey] boolValue]];
		    
    [oInputField setContinuousSpellCheckingEnabled:
	    [[settings objectForKey:WorldSpellCheckingKey] boolValue]];
    [oInputField setSmartInsertDeleteEnabled:
	    [[settings objectForKey:WorldSmartInsertDeleteKey] boolValue]];
						    
    [[self window] setViewsNeedDisplay:YES];

}

- (void)windowDidBecomeKey:(NSNotification *)notification
{
    //[[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeMainNotification object:nil];
}

- (void)windowWillClose:(NSNotification *)notification
{
    // This breaks it for some fucked up reason
    //[[NSNotificationCenter defaultCenter] removeObserver:self];
    // So we use this instead
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeMainNotification object:nil];
    [mSocket close];
        
    [[CantripPluginManager sharedPluginManager]
	    makePluginsPerformSelector:@selector(windowControllerWillClose:) withObject:self];
}

- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName
{
    if (childNumber == 1) return [displayName stringByDeletingPathExtension];
    else {
	if ([mWindowData objectForKey:@"ChildName"])
	    return [NSString stringWithFormat:@"%@ - %@", [displayName stringByDeletingPathExtension], [mWindowData objectForKey:@"ChildName"]];
	else
	    return [NSString stringWithFormat:@"%@ (%d)", [displayName stringByDeletingPathExtension], childNumber];
    }
}

- (void)windowDidResize:(NSNotification *)notification
{
    [oTermView autoscroll];
}

- (void)mainWindowDidChange:(NSNotification *)notification
{
    NSMenu *worldMenu = [[[NSApp mainMenu] itemWithTag:99] submenu];
    NSMenuItem *loggingItem = [worldMenu itemWithTag:1];
    NSMenuItem *connectItem = [worldMenu itemWithTag:2];

    if ([notification object] != [self window]) return;
	    
    if (isLogging) {
	[loggingItem setTitle:@"Stop Logging"];
    } else [loggingItem setTitle:@"Start Logging..."];
    
    switch (mState) {
	case WorldConnectedState:
	    [connectItem setTitle:@"Disconnect"];
	    break;
	case WorldConnectingState:
	    [connectItem setTitle:@"Cancel"];
	    break;
	case WorldStartState:
	case WorldDisconnectedState:
	    [connectItem setTitle:@"Connect"];
	    break;
    }
}

- (void)setupToolbar
{
    NSToolbar *worldToolbar = [[[NSToolbar alloc] initWithIdentifier:@"WorldToolbarIdentifier"] autorelease];
    
    [worldToolbar setAllowsUserCustomization: YES];
    [worldToolbar setAutosavesConfiguration: YES];
    [worldToolbar setDisplayMode:NSToolbarDisplayModeIconOnly];

    [worldToolbar setDelegate: self];
    
    [[self window] setToolbar:worldToolbar];
}

- (void)timerBeat:(id)sender
{
    [oTermView beatTimerAction:sender];

    /* Keep connection alive
    beatCount++;
    if (beatCount == 5)
    {
	if (mState == WorldConnectedState)
	{
		unsigned char data[2];
		data[0] = 255;
		data[1] = 246; //241
		[mSocket writeData:[NSData dataWithBytes:data length:2]];
	}
	beatCount = 0;
    }
    */
}

- (void)moveHistoryLocation:(int)dir
{
    if ([mCommandHistory count] == 0) return;
   	
    mHistoryLocation += dir;
    
    if (mHistoryLocation <= -1) mHistoryLocation = 0;
    else if (mHistoryLocation >= [mCommandHistory count]) 
        mHistoryLocation = [mCommandHistory count] - 1;
    
    [oInputField setStringValue:[mCommandHistory objectAtIndex:mHistoryLocation]];
    [oInputField selectText:self];
}

#pragma mark Interface Actions

- (void)saveButtonAction:(id)sender
{
    [[self document] saveDocument:self];
}

- (void)newWindowButtonAction:(id)sender
{
    [[self document] newWorldWindowAction:self];
}

- (IBAction)printDocument:(id)sender
{
    [oTermView print:self];
} 

- (IBAction)connectButtonAction:(id)sender
{
    if (mState == WorldConnectedState) {
		[self disconnect];
		[self handleConnectionEvent:ConnectionTerminated];
    } else if (mState == WorldConnectingState) {
		[self disconnect];
		// handle a connection event?
    } else {
	if ([[[self world] data] objectForKey:WorldDataAddressKey] &&
	    ![[[[self world] data] objectForKey:WorldDataAddressKey] isEqualToString:@""]) [self connect];
	else {
	    if ([[[self world] data] objectForKey:WorldDataPortKey] &&
		![[[[self world] data] objectForKey:WorldDataPortKey] isEqual:@""])
		[oAddressPanelPortField setStringValue:[[[self world] data] objectForKey:WorldDataPortKey]];
	    else [oAddressPanelPortField setStringValue:@"23"];
	    
	    [NSApp  beginSheet:oAddressPanel modalForWindow:[self window]
		 modalDelegate:self didEndSelector:NULL contextInfo:nil];
	}
    }
}

- (IBAction)settingsButtonAction:(id)sender
{
    [[CantripAppController controller] showSettingsPanel:self];
}

- (BOOL)textInputAction:(id)sender
{
    NSMutableString *theString, *newEchoString;
    NSAttributedString *echoString;
    int type, r;
    
    theString = [[NSMutableString alloc] initWithString:[sender stringValue]];
    newEchoString = [[NSMutableString alloc] initWithString:[sender stringValue]];
    
    [mCommandHistory addObject:[[[sender stringValue] copy] autorelease]];
    mHistoryLocation = [mCommandHistory count] - 1;
        
    
    echoString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n", newEchoString] 
						 attributes: [NSDictionary dictionaryWithObjectsAndKeys:
						     [[self document] valueForKey:@"EchoColor"], NSForegroundColorAttributeName,
						     [oTermView font], NSFontAttributeName,
						     nil
						     ]];
    
    [self handleInput:theString withEnvironment:nil];
    //[self sendString:theString];
    
    type = [[[[self world] data] objectForKey:WorldDataEchoKey] intValue];
    switch (type)  {
	case 1: [self echoString:theString]; break;
	case 2: [self echoString:@"\n"]; break;
    }
    
    if ([[[[self world] data] valueForKey:WorldClearInputKey] boolValue])
	[oInputField setStringValue:@""];
    [oInputField selectText:self];
    
    [theString release];
    
    return YES;
}

- (void)clearConsoleAction:(id)sender
{
    [oTermView clear];
}

- (void)customizeToolbarAction:(id)sender
{
    [[self window] runToolbarCustomizationPalette:self];
}

- (IBAction)toggleLoggingAction:(id)sender
{
    NSSavePanel *logPanel = [NSSavePanel savePanel];
    NSString *fileName;

    if (isLogging)
    {
	isLogging = NO;
	[mLogFileHandle closeFile];
	[mLogFileHandle release];
	[sender setTitle:@"Start Logging..."];
	[self messageString:@"Log finnished.\n"];
	return;
    }

    [logPanel setAccessoryView:oLogOptionsView];
    //[logPanel setRequiredFileType:@"txt"];
    fileName = [[NSString alloc] initWithFormat:@"%@ (%@).txt", 
	([[self document] fileName] ? [[[[self document] fileName] lastPathComponent] stringByDeletingPathExtension] : @"Untitled"),
	[[[NSDate date] description] substringToIndex:10]];

    if ([logPanel runModalForDirectory:NSHomeDirectory() file:fileName] == NSOKButton) {
	if ([self startLogForFile:[logPanel filename] retroactive:[oLogRetroactiveButton state]]) {
	    [sender setTitle:@"Stop Logging"];
	    isLogging = YES;
	}
    }
}

- (IBAction)menuConnectAction:(id)sender
{
    [self connectButtonAction:sender];
}

- (IBAction)addressPanelOKAction:(id)sender
{
    NSString *address = [oAddressPanelAddressField stringValue];
    NSString *port = [oAddressPanelPortField stringValue];
    
    if (![address isEqualToString:@""] && ![port isEqualToString:@""])
    {
	[NSApp endSheet:oAddressPanel];
	[oAddressPanel orderOut:nil];
	
	//[[[self document] worldData] setObject:address forKey:@"WorldAddress"];
	//[[[self document] worldData] setObject:[NSNumber numberWithUnsignedInt:[port intValue]] forKey:@"WorldPort"];
	[[[self world] data] setValue:address forKeyPath:WorldDataAddressKey];
	[[[self world] data] setValue:[NSNumber numberWithUnsignedInt:[port intValue]] forKeyPath:WorldDataPortKey];
	[[self world] updateChangeCount:NSChangeDone];
	
	[self connect];
    }
    else NSBeep();
}

- (IBAction)addressPanelCancelAction:(id)sender
{
    [NSApp endSheet:oAddressPanel];
    [oAddressPanel orderOut:nil];
}

- (IBAction)passwordPanelOKAction:(id)sender
{
    [self sendString:[oPasswordField stringValue]];
    
    [NSApp endSheet:oPasswordPanel];
    [oPasswordPanel orderOut:nil];
    [oPasswordField setStringValue:@""];
}


- (IBAction)passwordPanelCancelAction:(id)sender
{
    [NSApp endSheet:oPasswordPanel];
    [oPasswordPanel orderOut:nil];
    [oPasswordField setStringValue:@""];
}

- (BOOL)startLogForFile:(NSString *)filePath retroactive:(BOOL)retroactive
{
    NSFileManager *filem = [NSFileManager defaultManager];
	
    if ([filem fileExistsAtPath:filePath]) {
	if (![filem removeFileAtPath:filePath handler:nil]) {
	    NSRunAlertPanel(@"Logging Error", @"Error: Could not replace file.", @"OK", nil, nil);
	    return NO;
	}
    }

    [filem createFileAtPath:filePath contents:nil attributes:nil];
    mLogFileHandle = [[NSFileHandle fileHandleForUpdatingAtPath:filePath] retain];

    if (!mLogFileHandle) {
	NSRunAlertPanel(@"Logging Error", @"Error: Could not open file for writing.", @"OK", nil, nil);
	return NO;
    }

    if (retroactive) {
	[mLogFileHandle writeData:[[[[oTermView textView] textStorage] string] dataUsingEncoding:NSISOLatin1StringEncoding]];
    }

    [mLogFileHandle seekToEndOfFile];
    isLogging = YES;
    [self messageString:[NSString stringWithFormat:@"Logging started to '%@'\n", filePath]];

    return YES;
}

- (void)enterPasswordMode:(id)sender
{
    [NSApp  beginSheet:oPasswordPanel modalForWindow:[self window]
	 modalDelegate:self didEndSelector:NULL contextInfo:nil];
}

#pragma mark Delegate Handlers

- (BOOL)keyDownInWindow:(NSEvent *)event
{
    NSEnumerator *moduleEnumerator;
    id module;
    unsigned int key = [event keyCode];
     
    if ([event modifierFlags] & NSControlKeyMask) {
        if (key == 126) { [self moveHistoryLocation:-1]; return YES; }
        else if (key == 125) { [self moveHistoryLocation:1]; return YES; }
    }
    
    if (key == 48) { 
	[oInputField selectText:self]; return YES; 
    }
    
    moduleEnumerator = [[[CantripPluginManager sharedPluginManager] interfaceEventModules] objectEnumerator];
    while (module = [moduleEnumerator nextObject]) {
	if ([[module objectAtIndex:0] handleInterfaceEventAction:event]) return YES;
    }
    
    if (key != 115 && key != 116 && key != 119 && key != 121)
	[[self window] selectKeyViewPrecedingView:oTermView]; // <--- WHY DOES THIS WORK!!!? WTF?
							      // should be selectKeyViewFollowingView
    
    return NO;
}

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted 
{
    NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease];
    NSEnumerator *e;
    id module;
    
    if ([itemIdent isEqualToString:WorldToolbarItemConnect]) {
	NSView *connectView = [[NSView alloc] initWithFrame:NSMakeRect(0,0,64,32)];
	NSButton *buttonView = [[NSButton alloc] initWithFrame:NSMakeRect(0,0,64,32)];
	[buttonView setImage:[NSImage imageNamed:@"Connect"]];
	[buttonView setImagePosition:NSImageOnly];
	[buttonView setButtonType:NSRegularSquareBezelStyle];
	[buttonView setBordered:NO];
	[buttonView setTarget:self];
	[buttonView setAction:@selector(connectButtonAction:)];
	[connectView addSubview:buttonView];
	
	[toolbarItem setLabel: @"Connect/Disconnect"];
	[toolbarItem setToolTip: @"Connect/Disconnect from World"];
	[toolbarItem setPaletteLabel:@"Connect/Disconnect"];
	//[toolbarItem setImage: [NSImage imageNamed:@"Connect"]];
	[toolbarItem setView:connectView];
	[toolbarItem setMinSize:NSMakeSize(64, 32)];
	[toolbarItem setMaxSize:NSMakeSize(64, 32)];
	
	[toolbarItem setTarget: self];
	[toolbarItem setAction: @selector(connectButtonAction:)];
    } else if ([itemIdent isEqualToString:WorldToolbarItemSettings]) {
	[toolbarItem setLabel: @"Settings"];
	[toolbarItem setToolTip: @"Display settings panel"];
	[toolbarItem setPaletteLabel:@"Settings"];
	[toolbarItem setImage: [NSImage imageNamed:@"Settings.tiff"]];
	[toolbarItem setMinSize:NSMakeSize(32, 32)];
	[toolbarItem setMaxSize:NSMakeSize(32, 32)];
	[toolbarItem setTarget: self];
	[toolbarItem setAction: @selector(settingsButtonAction:)];
    } else if ([itemIdent isEqualToString:WorldToolbarItemClean]) {
	[toolbarItem setLabel:@"Clear"];
	[toolbarItem setToolTip:@"Clear display output."];
	[toolbarItem setPaletteLabel:@"Clear"];
	[toolbarItem setImage:[NSImage imageNamed:@"Clear.tiff"]];
	[toolbarItem setTarget:self];
	[toolbarItem setAction:@selector(clearConsoleAction:)];
    } else if ([itemIdent isEqualToString:WorldToolbarItemSave]) {
	[toolbarItem setLabel:@"Save"];
	[toolbarItem setToolTip:@"Save world file."];
	[toolbarItem setPaletteLabel:@"Save"];
	[toolbarItem setImage:[NSImage imageNamed:@"Save.tiff"]];
	[toolbarItem setTarget:self];
	[toolbarItem setAction:@selector(saveButtonAction:)]; //saveDocument
    } else if ([itemIdent isEqualToString:WorldToolbarItemNewWindow]) {
	[toolbarItem setLabel:@"New Window"];
	[toolbarItem setToolTip:@"Create a new connection window for this world."];
	[toolbarItem setPaletteLabel:@"New Window"];
	[toolbarItem setImage:[NSImage imageNamed:@"NewWorldWindow.tiff"]];
	[toolbarItem setTarget:self];
	[toolbarItem setAction:@selector(newWindowButtonAction:)];
    } else {
	e = [[[CantripPluginManager sharedPluginManager] pluginModules] objectEnumerator];
	while (module = [e nextObject]) {
	    if ([module respondsToSelector:@selector(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:controller:)]) {
		NSToolbarItem *atoolbarItem;
		atoolbarItem = [module toolbar:toolbar itemForItemIdentifier:itemIdent willBeInsertedIntoToolbar:willBeInserted controller:self];
		return atoolbarItem;
	    }
	}
    }
    
    return toolbarItem;
}

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar 
{
    NSMutableArray *items = [[NSMutableArray alloc] init];    
    
    [items addObjectsFromArray:[NSArray arrayWithObjects:
	WorldToolbarItemConnect, 
	NSToolbarFlexibleSpaceItemIdentifier,
	NSToolbarCustomizeToolbarItemIdentifier,
	WorldToolbarItemSettings,
	nil]];
    /*
     for (pluginIndex = 0; pluginIndex < [[[PluginsManager sharedPluginsManager] pluginsArray] count]; pluginIndex++)
     {
	 CantripPlugin *plugin = [[[PluginsManager sharedPluginsManager] pluginsArray] objectAtIndex:pluginIndex];
	 NSArray *otherItems;
	 
	 otherItems = [plugin toolbarDefaultItemIdentifiers:toolbar controller:self];
	 if (otherItems) [items addObjectsFromArray:otherItems];
     }
     */
    
    return [items autorelease];
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar 
{
    NSMutableArray *items = [[NSMutableArray alloc] init];    
    
    [items addObjectsFromArray:[NSArray arrayWithObjects:
	WorldToolbarItemConnect, WorldToolbarItemSettings,
	WorldToolbarItemClean,
	NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier,
	NSToolbarSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier,
	WorldToolbarItemSave, WorldToolbarItemNewWindow,
	NSToolbarPrintItemIdentifier,
	nil]];
    
    /*
     for (pluginIndex = 0; pluginIndex < [[[PluginsManager sharedPluginsManager] pluginsArray] count]; pluginIndex++)
     {
	 CantripPlugin *plugin = [[[PluginsManager sharedPluginsManager] pluginsArray] objectAtIndex:pluginIndex];
	 NSArray *otherItems = nil;
	 
	 otherItems = [plugin toolbarAllowedItemIdentifiers:toolbar controller:self];
	 if (otherItems) [items addObjectsFromArray:otherItems];
     }
     */
    
    return [items autorelease];
}

- (void)toolbarWillAddItem:(NSNotification *)notification
{
    NSToolbarItem *addedItem = [[notification userInfo] objectForKey: @"item"];
    
    if([[addedItem itemIdentifier] isEqual:WorldToolbarItemConnect])
    {
	connectButtonItem = [addedItem retain];
	[self setStateAndUpdate:mState];
    }
    
    /*
     [[PluginsManager sharedPluginsManager]
	makePluginsPerformSelector:@selector(toolbarWillAddItem:controller:) 
			withObject:notification withObject:self];
     */
}

- (void)toolbarDidRemoveItem:(NSNotification *)notification
{
    NSToolbarItem *removedItem = [[notification userInfo] objectForKey: @"item"];
    
    if (removedItem == connectButtonItem)
    {
	[connectButtonItem autorelease];
	connectButtonItem = nil;
    }
    
    /*
     [[PluginsManager sharedPluginsManager]
	makePluginsPerformSelector:@selector(toolbarDidRemoveItem:controller:) 
			withObject:notification withObject:self];
     */
}


- (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange 
    replacementString:(NSString *)replacementString
{
    if ([replacementString isEqual:@"\n"]) { 	
	NSEvent *theEvent = [NSApp currentEvent];
	
	if ([theEvent modifierFlags] & NSControlKeyMask) {
	    [self keyDownInWindow:theEvent]; 
	    return NO;
	}
	
	if ([theEvent modifierFlags] & NSAlternateKeyMask) {
	    return YES;
	}
	
	if ([self textInputAction:textView]) return NO;
    }
    
    return YES;
}

- (void)displayView:(CantripDisplayView *)display handleFormattedString:(NSMutableAttributedString *)str
{
    if (isLogging && oTermView == display) {
	[mLogFileHandle writeData:[[str string] dataUsingEncoding:NSISOLatin1StringEncoding]];
    }
}

#pragma mark Accessors

- (CantripWorldDocument *)world
{
    return [self document];
}

- (NSString *)controllerID
{
    return uniqueID;
}

- (NSMutableDictionary *)childData;
{
    return mWindowData;
}

- (CantripSocket *)socket
{
    return mSocket;
}

- (unsigned int)state
{
    return mState;
}

- (CantripDisplayView*)termView
{
    return oTermView;
}

- (CantripInputField *)inputField
{
    return oInputField;
}

- (void)setMsgBar:(NSString *)msg
{
    [oMsgBar setStringValue:msg];
}

- (NSString *)msgBar
{
    return [oMsgBar stringValue];
}

#pragma mark Actions

- (void)sendString:(NSString *)str
{
    if ([mSocket connected]) {
	NSArray *lines = [str componentsSeparatedByString:@"\n"];
	unsigned int i;
	for (i = 0; i < [lines count]; i++) {
	    switch ([[[[self world] data] objectForKey:WorldReturnStyleKey] intValue]) {
		case 0: [mSocket writeString:[NSString stringWithFormat:@"%@\n\r", [lines objectAtIndex:i]] encoding:NSUTF8StringEncoding]; break;
		case 1: [mSocket writeString:[NSString stringWithFormat:@"%@\r\n", [lines objectAtIndex:i]] encoding:NSUTF8StringEncoding]; break;
		case 2: [mSocket writeString:[NSString stringWithFormat:@"%@\n", [lines objectAtIndex:i]] encoding:NSUTF8StringEncoding]; break;
		case 3: [mSocket writeString:[NSString stringWithFormat:@"%@\r", [lines objectAtIndex:i]] encoding:NSUTF8StringEncoding]; break;
	    }
	}
    }
}

- (void)echoString:(NSString *)str
{
    NSAttributedString *echoString;
    echoString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n", str] 
						 attributes: [NSDictionary dictionaryWithObjectsAndKeys:
						     [[[self world] data] objectForKey:WorldEchoColor], NSForegroundColorAttributeName,
						     [oTermView font], NSFontAttributeName,
						     [NSCursor pointingHandCursor], NSCursorAttributeName,
						     nil]];
    [oTermView appendAttributedString:[echoString autorelease]];
}

- (void)sendStringAndEcho:(NSString *)str
{
    [self echoString:str];
    [self sendString:str];
}

- (void)messageString:(NSString *)str
{
    NSAttributedString *msgString = 
    [[NSAttributedString alloc] initWithString:str attributes:
	[NSDictionary dictionaryWithObjectsAndKeys:
	    [[[self world] data] objectForKey:WorldMessagesColor], NSForegroundColorAttributeName,
	    [oTermView font], NSFontAttributeName, nil]];
    
    NSLog(str);
    [oTermView appendAttributedString:[msgString autorelease]];
}

- (void)handleInput:(NSString *)script withEnvironment:(NSDictionary *)env
{
    NSMutableString *cleanedString = [[NSMutableString alloc] init];
    NSMutableString *otherString = [[NSMutableString alloc] initWithString:script];
    NSArray *commands;
    NSString *line, *finalString;
    int start = 0, end = 0, r, pluginIndex;
    BOOL addedLines = NO;
        
    MakeUniformLineEndings(otherString);
    commands = [otherString componentsSeparatedByString:@"\n"];
    
    for (start = 0; start < [commands count]; start++) {
	line = [commands objectAtIndex:start];
	if ([line hasPrefix:@"@@"]) {
	    NSString *scriptType;
	    if (++start >= [commands count]) break;
	    end = start + 1;

	    scriptType = [line substringFromIndex:2];
	    
	    while (end < [commands count]) {
		if ([[commands objectAtIndex:end] isEqual:@"@@end"]) break;
		end++;
	    } 
	    if (end >= [commands count]) end = [commands count];
	    
	    finalString = [[commands subarrayWithRange:NSMakeRange(start, end - start)] componentsJoinedByString:@"\n"];
		    
	    start = end;
	    
	    //!!TODO: add the contents of the passed dictionary to this one
	    [[CantripScriptManager sharedScriptManager] performScript:finalString ofType:scriptType withEnvironment:
		[NSDictionary dictionaryWithObjectsAndKeys:
		    self, @"worldController", nil]];
	    
	} else {
	    if ([cleanedString length] > 0) [cleanedString appendString:@"\n"];
	    [cleanedString appendString:line];
	    addedLines = YES;
	}
    }
    
    if (addedLines) {
	if ([self state] == WorldConnectedState)
	    [self sendString:cleanedString];
    }
    
    [cleanedString release];
    [otherString release];
}

#pragma mark Connection stuff

- (void)setState:(int)s
{
    mState = s;
}

- (void)setStateAndUpdate:(int)s
{
    NSWindow *mainWindow = [NSApp mainWindow];
    NSMenu *worldMenu = [[[NSApp mainMenu] itemWithTag:99] submenu];
    NSMenuItem *connectItem = [worldMenu itemWithTag:2];
        
    [self setState:s];
    
    switch(mState) {
	case WorldStartState:
	    [oStatusBar setStringValue:@"Idle"];
	    if (connectButtonItem) {
		[connectButtonItem setLabel:@"Connect"];
		if (mainWindow == [self window]) [connectItem setTitle:@"Connect"];
		//[connectButtonItem setImage:[NSImage imageNamed:@"Connect.psd"]];
		[[[[connectButtonItem view] subviews] lastObject] setImage:[NSImage imageNamed:@"Connect"]];
		if (oProgressWidget) [oProgressWidget stopAnimation:self];
	    }
		break;
	case WorldConnectingState:
	    [oStatusBar setStringValue:@"      Connecting..."];
	    if (connectButtonItem) {
		[connectButtonItem setLabel:@"Cancel"];
		if (mainWindow == [self window]) [connectItem setTitle:@"Cancel"];
		//[connectButtonItem setImage:[NSImage imageNamed:@"Connecting.psd"]];
		[[[[connectButtonItem view] subviews] lastObject] setImage:[NSImage imageNamed:@"Connected"]];
		if (oProgressWidget) [oProgressWidget startAnimation:self];
	    }
	    break;
	case WorldConnectedState:
	    [oStatusBar setStringValue:@"Connected"];
	    if (connectButtonItem) {
		[connectButtonItem setLabel:@"Disconnect"];
		if (mainWindow == [self window]) [connectItem setTitle:@"Disconnect"];
		//[connectButtonItem setImage:[NSImage imageNamed:@"Connected.psd"]];
		[[[[connectButtonItem view] subviews] lastObject] setImage:[NSImage imageNamed:@"Connected"]];
		if (oProgressWidget) [oProgressWidget stopAnimation:self];
	    }
	    break;
	case WorldDisconnectedState:
	    [oStatusBar setStringValue:@"Idle"];
	    if (connectButtonItem) {
		[connectButtonItem setLabel:@"Connect"];
		if (mainWindow == [self window]) [connectItem setTitle:@"Connect"];
		//[connectButtonItem setImage:[NSImage imageNamed:@"Connect.psd"]];
		[[[[connectButtonItem view] subviews] lastObject] setImage:[NSImage imageNamed:@"Connect"]];
		if (oProgressWidget) [oProgressWidget stopAnimation:self];
	    }
	    break;
    }
}

- (void)connect
{
    [mSocket open];
    [NSThread detachNewThreadSelector:@selector(connectThread:) 
			     toTarget:self withObject:
	[NSArray arrayWithObjects:[mThreadConnection sendPort], 
		[mThreadConnection receivePort], nil]];
}

- (void)disconnect
{
    [mSocket close];
    [self setStateAndUpdate:WorldDisconnectedState];
    [self messageString:@"Disconnected.\n"];
}

- (void)connectThread:(NSArray *)ports
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    id proxy = [[NSConnection connectionWithReceivePort:[ports objectAtIndex:0] 
					       sendPort:[ports objectAtIndex:1]] rootProxy];
    NSString *serverAddress;
    unsigned int serverPort;
    
    serverAddress = [[[self world] data] valueForKeyPath:WorldDataAddressKey];
    serverPort = [[[[self world] data] valueForKey:WorldDataPortKey] intValue];
    
    [proxy setStateAndUpdate:WorldConnectingState];
    [proxy messageString:[NSString stringWithFormat:@"Connecting to %@...\n", serverAddress]];
    
    if (![mSocket connectToHost:serverAddress port:serverPort timeout:5.0]) {
	[proxy setStateAndUpdate:WorldDisconnectedState];
	[proxy messageString:[NSString stringWithFormat:@"Connection could not be established!\n"]];
	[proxy handleConnectionEvent:ConnectionUnsuccessful];
    }
    
    if ([[[[self world] data] valueForKey:WorldEnableMCCP] boolValue]) {
	handlingMCCP = YES;
	mccp_state = mudcompress_new();
    } else handlingMCCP = NO;
    
    [pool release];
}    

- (void)handleConnectionEvent:(WorldConnectionEvent)event
{
    NSEnumerator *e = [[[CantripPluginManager sharedPluginManager] interfaceEventModules] objectEnumerator];
    id module;
    
    while (module = [e nextObject]) {
	if ([[module objectAtIndex:0] respondsToSelector:@selector(handleConnectionEventAction:controller:)])
	    [[module objectAtIndex:0] handleConnectionEventAction:event controller:self];
    }
    
}

static char *terminal_types[] = {
    "cantrip",
    "vt102",
    "ansi",
    "unkown",
    NULL};

/* Hex dump from a telnet client
< 00000000 ff fb 25 ff fd 26 ff fb 26 ff fd 03 ff fb 18 ff # ..%..&..&.......
< 00000010 fb 1f ff fb 20 ff fb 21 ff fb 22 ff fb 27 ff fd # .... ..!.."..'..
< 00000020 05                                              # .
*/

- (void)handleIACParsing:(NSMutableData *)data
{
    unsigned char response[32];
    unsigned char byte;
    unsigned int i;
    
    for (i = 0; i < [data length];) {
	byte = ((unsigned char*)[data bytes])[i];
	
	if (IAC_STATE == IAC) {
	    if (byte == IAC) {
		if (IAC_SB) { 
		    [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
		    continue;
		}
		else IAC_STATE = 0; i++; continue;
	    }
	    
	    [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
	    if (byte == WILL) {
		IAC_STATE = WILL; 
	    } else if (byte == WONT) {
		IAC_STATE = WONT; 
	    } else if (byte == DO) {
		IAC_STATE = DO; 
	    } else if (byte == DONT) {
		IAC_STATE = DONT; 
	    } else if (byte == EL) {
		IAC_STATE = 0;
		// Handle ERASE LINE
		if ([[[[self world] data] valueForKey:@"handleEraseLine"] boolValue]) {
		    [oTermView eraseLine];
		    // erase the rest of this line
		}
	    } else if (byte == EC) {
		IAC_STATE = 0;
		// Handle ERASE CHARACTER
		if ([[[[self world] data] valueForKey:@"handleEraseCharacter"] boolValue]) {
		    //[oTermView eraseCharacter];
		}
	    } else if (byte == SE) {
		IAC_STATE = 0;
		IAC_SB = 0;
		IAC_SB_TYPE = 0;
		// Handle sub-negotiation	
		if (IAC_SB_TYPE == TERMINAL_TYPE) {
		    if (ttrequest_type == 1) {
			if (terminal_types[ttindex] == NULL) {
			    response[0] = IAC;
			    response[1] = WONT;
			    response[3] = TERMINAL_TYPE;
			    [mSocket write:response amount:3];
			} else {
			    response[0] = IAC;
			    response[1] = SB;
			    response[2] = TERMINAL_TYPE;
			    response[3] = 0;
			    [mSocket write:response amount:4];
			    [mSocket write:terminal_types[ttindex] amount:strlen(terminal_types[ttindex])];
			    response[0] = IAC;
			    response[1] = SE;
			    [mSocket write:response amount:2];
			}
		    }
		}
	    } else {
		IAC_STATE = 0;
		if (byte == NOP) {
		    // No operation
		} else if (byte == GA) {
		    // line is a prompt
		    NSLog(@"GA encountered");
		} else if (byte == END_OF_RECORD) {
		    // line is a prompt
		    NSLog(@"EOR encountered");
		}
	    }
	} else if (IAC_STATE == WILL) {
	    // Server wants to enable an option
	    [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
	    if (byte == ECHO) {
		// Server will handle echoing now
		// i.e. this is probably a password prompt
		[self enterPasswordMode:nil];
	    } else if (byte == END_OF_RECORD) {
		// Handle EOR (line prompts and stuff)
		response[0] = IAC;
		response[1] = DO;
		response[3] = byte;
		[mSocket write:response amount:3];
	    } else {
		// We don't recognize this option.  Tell the server.
		//NSLog(@"Unknown request: %d", byte);
		response[0] = IAC;
		response[1] = DONT;
		response[3] = byte;
		[mSocket write:response amount:3];
	    }
		
	    IAC_STATE = 0; continue;
	} else if (IAC_STATE == WONT) {
	    [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
	    if (byte == ECHO) {
		// Client should handle echoing now
		// i.e. password prompt is done
		
	    } 
	    IAC_STATE = 0; continue;
	} else if (IAC_STATE == DO) {
	    [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
	    if (byte == TERMINAL_TYPE) {
		// Send the server the terminal type
	    } else if (byte == NAWS) {
		// Send the server display info (width/height)
	    } else if (byte == TERMINAL_TYPE) {
		// Server wants terminal type
		response[0] = IAC;
		response[1] = WILL;
		response[3] = byte;
		[mSocket write:response amount:3];
	    }
	    IAC_STATE = 0; continue;
	} else if (IAC_STATE == DONT) {
	    [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
	    if (byte == ECHO) {
		// Client should handle echoing now
		// i.e. password prompt is done
		
	    }
	    IAC_STATE = 0; continue;
	} else if (IAC_STATE == 0) {
	    if (byte == IAC) {
		[data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
		IAC_STATE = IAC;
	    } else if (IAC_SB) {
		[data replaceBytesInRange:NSMakeRange(i, 1) withBytes:nil length:0];
		if (IAC_SB < 2) {
		    IAC_SB_TYPE = byte;
		    IAC_SB = 2;
		} else {
		    // Gather sub-negotiation info
		    if (IAC_SB_TYPE == TERMINAL_TYPE) {
			ttrequest_type = byte;
		    }
		}
	    } else {
		i++;
	    }
	}
    }
}

- (void)socketConnected:(CantripSocket*)inSocket
{
    unsigned char iac_msg[3];
    [self setStateAndUpdate:WorldConnectedState];
    [self messageString:@"Connected.\n"];
    
    iac_msg[0] = IAC;
    iac_msg[1] = WILL;
    iac_msg[2] = TERMINAL_TYPE;
    [mSocket write:iac_msg amount:3];
    iac_msg[0] = IAC;
    iac_msg[1] = WILL;
    iac_msg[2] = AUTHENTICATION;
    [mSocket write:iac_msg amount:3];
    iac_msg[0] = IAC;
    if ([[[[self world] data] valueForKey:WorldHandleNAWSKey] boolValue]) iac_msg[1] = WILL;
    else iac_msg[1] = WONT;
    iac_msg[2] = NAWS;
    [mSocket write:iac_msg amount:3];
    iac_msg[0] = IAC;
    iac_msg[1] = WILL;
    iac_msg[2] = REMOTE_FLOW_CONTROL;
    [mSocket write:iac_msg amount:3];
    iac_msg[0] = IAC;
    iac_msg[1] = DONT;
    iac_msg[2] = STATUS;
    [mSocket write:iac_msg amount:3];
}

- (void)socketDisconnected:(CantripSocket*)inSocket
{
    [self setStateAndUpdate:WorldDisconnectedState];
    [self messageString:@"Connection lost.\n"];
    [inSocket close];

    if (handlingMCCP) {
	mudcompress_delete(mccp_state);
    }
}

- (void)socket:(CantripSocket*)inSocket connectionTimedOut:(NSTimeInterval)inTimeout
{
    [self setStateAndUpdate:WorldDisconnectedState];
    [self messageString:[NSString stringWithFormat:@"Connection timed out!\n"]];
    [inSocket close];
}

- (void)socket:(CantripSocket*)inSocket dataAvailable:(unsigned)inAmount
{
    NSMutableData *buffer = [[[NSMutableData alloc] init] autorelease];
    char *mccp_response;
    
    [buffer setLength:0];
    
    // Do MCCP processing here
    if (handlingMCCP) {
	NSData *data = [mSocket readData];
	unsigned int size;
	
	mudcompress_receive(mccp_state, [data bytes], [data length]);
	while (size = mudcompress_pending(mccp_state)) {
	    int oldsize = [buffer length];
	    
	    [buffer increaseLengthBy:size];
	    mudcompress_get(mccp_state, [buffer mutableBytes] + oldsize, size);
	    
	    if (mccp_response = mudcompress_response(mccp_state)) {
		[mSocket write:mccp_response amount:strlen(mccp_response)];
	    }
	}
	
	if (mudcompress_error(&mccp_state)) {
	    // fatal mccp error
	    [mSocket close];
	    return;
	}
    } else {
	[mSocket readOntoData:buffer];
    }
    
    // Do IAC processing here
    [self handleIACParsing:buffer];
    
    // pass to ANSI parser
    [oTermView processString:
	[[[NSString alloc] initWithData:buffer encoding:NSISOLatin1StringEncoding] autorelease]];
}

- (void)socketSentData:(CantripSocket*)inSocket
{
    
}

@end

void MakeUniformLineEndings(NSMutableString *string)
{
    char c; int i;
    for (i = 0; i < [string length]; i++) {
	c = [string characterAtIndex:i];
	if (c == '\r') [string replaceCharactersInRange:NSMakeRange(i,1) withString:@"\n"];
    }
}

